package com.unipay.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.unipay.common.helper.JwtHelper;
import com.unipay.common.result.Result;
import com.unipay.common.result.ResultCodeEnum;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @Project: yitong_parent
 * @Author: C.yu
 * @Time: 2023-05-14 13:12:38
 * @Desc: TODO  三、用户认证与网关整合
 * 思路：
 * 1.所有请求都会经过服务网关，服务网关对外暴露服务，在网关进行统一用户认证；
 * 2.既然要在网关进行用户认证，网关得知道对 “哪些url” 进行认证，所以我们得---对ur制定规则
 */
// Api接口异步请求的，我们采取url规则匹配，如：/api/**/auth/**，如凡是满足该规则的都必须用户认证

/*
 * @desc: 全局Filter，统一处理：会员登录，与外部不允许访问的服务
 */
@Component
public class AuthGlobalFilter implements Ordered, GlobalFilter {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        System.out.println("request.getURI().getPath()：" + path);
        //内部服务接口，不允许外部访问
        if (antPathMatcher.match("/**/inner/**", path)) {
            ServerHttpResponse response = exchange.getResponse();
            //return out(response, ResultCodeEnum.PERMISSION);
            return out(response, ResultCodeEnum.PERMISSION);    // 209
        }
        /* TODO api接口，校验用户必须登录
         * 第一次登录login：设置token、cookie
         * 后面登录：通过全局网关AuthGlobalFilter类，匹配路径/api/../auth/.."，获取token、userId后，----才能访问
         * 详见：UserInfoApiController类，方法
         */
        //if (antPathMatcher.match("/api/**/", path)) {
        if (antPathMatcher.match("/api/**/auth/**", path)) {
            Long userId = this.getUserId(request);
            if (StringUtils.isEmpty(userId)) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response, ResultCodeEnum.LOGIN_AUTH);    // 208
            }
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * api接口鉴权失败返回数据
     *
     * @param response
     */
    private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        Result result = Result.build(null, resultCodeEnum);
        byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //指定编码，否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    /*
     * TODO 在服务网关中，判断：用户 “登录状态”
     * 在网关中如何获取 “用户信息”：
     *      我们统一从header头信息中获取
     * 如何判断用户信息合法：
     *      登录时我们返回用户token，在服务网关中获取到token后，我在到redis中去查看用户id，
     *      如果用户id存在，则token合法，否则不合法
     * 请求服务器端接口时我们默认带上token，需要登录的接口如果token没有或者token过期，
     * 服务器端会返回208状态，然后发送登录事件打开登录弹出层登录
     **/
    //获取当前登录用户id
    private Long getUserId(ServerHttpRequest request) {
        String token = "";
        List<String> tokenList = request.getHeaders().get("token");
        if (null != tokenList) {
            token = tokenList.get(0);
            System.out.println("token: " + token);
        }
        if (!StringUtils.isEmpty(token)) {
            return JwtHelper.getUserId(token);
        }
        return null;
    }
}
